﻿using Minio.DataModel;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace Minio
{
    public partial class MinioClient
    {
        private static string UPLOAD_ID = "uploadId";

        /**
     * 在进行多文件上传之前，先初始化，获取uploaderId等信息
     *
     * @param bucketName Bucket name.
     * @param objectName Object name in the bucket.
     */
        public async Task<string> InitMultUploadAsync(string bucketName, string objectName,
                                     Dictionary<string, string> headerMap, string contentType)
        {

            if (headerMap == null)
            {
                headerMap = new Dictionary<string, string>();
            }

            if (contentType == null)
            {
                if (!headerMap.ContainsKey("Content-Type"))
                {
                    headerMap.Add("Content-Type", "application/octet-stream");
                }
            }
            else
            {
                headerMap.Add("Content-Type", contentType);
            }


            string uploadId = await this.NewMultipartUploadAsync(bucketName, objectName, new Dictionary<string, string>(), headerMap, default).ConfigureAwait(false);
            return uploadId;
        }

        /**
     * 执行分断断点上传
     *
     * @param uploadId   上传ID
     * @param bucketName
     * @param objectName
     * @param size
     * @param data
     * @return
     * @throws Exception
     */
        public async Task<Part[]> MakeMultUploadAsync(string uploadId, string bucketName, string objectName, long size, object data, Part[] parts)
        {
            /* Multipart upload */
            Part[]
            totalParts = parts;
            if (totalParts == null)
            {
                dynamic multiPartInfo = utils.CalculateMultiPartSize(size);
                double partSize = multiPartInfo.partSize;
                double partCount = multiPartInfo.partCount;
                double lastPartSize = multiPartInfo.lastPartSize;
                totalParts = new Part[(int)partCount];
                for (int i = 0; i < totalParts.Length; i++)
                {
                    totalParts[i] = new Part() { PartNumber = i + 1 };
                    if (i != totalParts.Length - 1)
                    {
                        totalParts[i].Size = ((long)partSize);
                    }
                    else
                    {
                        totalParts[i].Size = ((long)lastPartSize);
                    }
                }
            }


            for (int partNumber = 1; partNumber <= totalParts.Length; partNumber++)
            {
                try
                {
                    if (totalParts[partNumber - 1].PartSize() != 0)
                    {
                        long expectedReadSize = totalParts[partNumber - 1].Size;

                        // string string stream int ? string int
                        string etag = await PutObjectAsync(bucketName, objectName, data, (int)expectedReadSize, null,
                                uploadId, partNumber);

                        totalParts[partNumber - 1].ETag = etag;
                        //totalParts[partNumber - 1].setState(0);
                        //TODO 模拟中途出错，如果是3的倍数，就退出
                    }
                }
                catch (Exception e)
                {
                    //出错后打出异常，继续执行，这样就可以得到出错的卷
                    //totalParts[partNumber - 1].setState(-1);
                    Console.WriteLine(e.StackTrace);
                }
            }

            return totalParts;
        }


        /**
     * Executes put object and returns ETag of the object.
     *
     * @param bucketName Bucket name.
     * @param objectName Object name in the bucket.
     * @param length     Length of object data.
     * @param data       Object data.
     * @param uploadId   Upload ID of multipart put object.
     * @param partNumber Part number of multipart put object.
     */
        private async Task<string> PutObjectAsync(string bucketName, string objectName, object data, int length,
                                 Dictionary<string, string> metaData, string uploadId, int partNumber)
        {

            //string contentType = metaData["Content-Type"];
            //if (uploadId != null)
            //{
            //    metaData = new Dictionary<string, string>();
            //}

            //foreach (KeyValuePair<string, string> kv in sseHeaders)
            //{
            //    metaData.Add(kv.Key, kv.Value);
            //}
            metaData = new Dictionary<string, string>();
            metaData["Content-Type"] = "application/octet-stream";
            var request = await this.CreateRequest(Method.PUT, bucketName,
                                                     objectName: objectName,
                                                     contentType: "application/octet-stream",
                                                     headerMap: metaData,
                                                     body: data)
                                    .ConfigureAwait(false);
            if (!string.IsNullOrEmpty(uploadId) && partNumber > 0)
            {
                request.AddQueryParameter("uploadId", $"{uploadId}");
                request.AddQueryParameter("partNumber", $"{partNumber}");
            }

            var response = await this.ExecuteTaskAsync(this.NoErrorHandlers, request, default).ConfigureAwait(false);


            //Dictionary<string, string> queryParamMap = null;
            //var request = await this.CreateRequest(Method.PUT, bucketName, objectName: objectName,
            //                headerMap: headerMap).ConfigureAwait(false);
            //if (partNumber > 0 && uploadId != null && !"".Equals(uploadId))
            //{
            //    //queryParamMap = new Dictionary<string, string>();
            //    //queryParamMap.Add("partNumber", partNumber.Tostring());
            //    //queryParamMap.Add(UPLOAD_ID, uploadId);
            //    request.AddParameter("partNumber", partNumber);
            //    request.AddParameter(UPLOAD_ID, uploadId);
            //}

            //var response = await this.ExecuteTaskAsync(this.NoErrorHandlers, request, default).ConfigureAwait(false);
            //response = executePut(bucketName, objectName, headerMap, queryParamMap, data, length);

            //var contentBytes = response.Content;
            return response.Content;
            //return response.().etag();
        }

        /**
       * 执行分断断点上传
       *
       * @param size
       * @param
       * @return
       * @throws Exception
       */
        public Part[] MakeMultUpload(long size, Part[] parts)
        {
            /* Multipart upload */
            Part[] totalParts = parts;
            if (totalParts == null)
            {
                dynamic multiPartInfo = utils.CalculateMultiPartSize(size);
                double partSize = multiPartInfo.partSize;
                double partCount = multiPartInfo.partCount;
                double lastPartSize = multiPartInfo.lastPartSize;
                totalParts = new Part[(int)partCount];
                for (int i = 0; i < totalParts.Length; i++)
                {
                    totalParts[i] = new Part() { PartNumber = i + 1 };
                    if (i != totalParts.Length - 1)
                    {
                        totalParts[i].Size = ((long)partSize);
                    }
                    else
                    {
                        totalParts[i].Size = ((long)lastPartSize);
                    }
                }
            }
            return totalParts;
        }

        /// <summary> 
        /// 将一个object对象序列化，返回一个byte[]         
        /// </summary> 
        /// <param name="obj">能序列化的对象</param>         
        /// <returns></returns> 
        public static byte[] ObjectToBytes(object obj)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                return ms.GetBuffer();
            }
        }

        /**
    * 执行分断断点上传
    *
    * @param uploadId   上传ID
    * @param bucketName
    * @param objectName
    * @param
    * @param data
    * @return
    * @throws Exception
    */
        public async Task<Part[]> MakeMultUploadAsync(string uploadId, string bucketName, string objectName, long expectedReadSize, Object data, Part[] parts, int partNumber)
        {
            /* Multipart upload */
            Part[] totalParts = parts;
            try
            {
                Stream stream = (Stream)data;
                byte[] bytes = new byte[stream.Length];
                await stream.ReadAsync(bytes, 0, bytes.Length);
               
                Dictionary<string, string> metaData = new Dictionary<string, string>();
                metaData["Content-Type"] = "application/octet-stream";
                string etag = await PutObjectAsync(bucketName, objectName, uploadId, partNumber, bytes, metaData, new Dictionary<string, string>(), default);
               
                totalParts[partNumber - 1].ETag = (etag);
            }
            catch (Exception e)
            {
                //出错后打出异常，继续执行，这样就可以得到出错的卷
                //totalParts[partNumber - 1].setState(-1);
                Console.WriteLine(e.StackTrace);
            }

            return totalParts;
        }

        public Task CommitMultUploadAsync(string uploadId, string bucketName, string objectName, Part[] parts)
        {
            Dictionary<int, string> etags = new Dictionary<int, string>();
            for (int partNumber = 1; partNumber <= parts.Length; partNumber++)
            {
                etags[partNumber] = parts[partNumber - 1].ETag;
            }
            return this.CompleteMultipartUploadAsync(bucketName, objectName, uploadId, etags, default);
        }
    }
}

